package wator.model.strategies;

import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import wator.model.Agent;
import wator.model.Constants;
import wator.model.IntegerVector;
import wator.model.MovementStrategy;
import wator.model.Neighborhood;
import wator.model.Neighborhood.Neighbor;
import wator.model.Simulation;

/**
 * This movement strategy implements entirely random movements. This is the
 * movement concept used in the original paper and can be used to reproduce its
 * results. This strategy observes the simulation to get notified when the
 * size vector changes.
 */
public class RandomMovementStrategy implements MovementStrategy, Observer {
	
	private int reach;
	private int dim;
	private IntegerVector reachVector, halfReachVector, nullVector;
	
	/**
	 * Sole constructor. Initializes the reach with <code>Constants.
	 * DEFAULT_REACH</code> and set internal vectors accordingly.
	 */
	public RandomMovementStrategy() {
		super();
		reach = Constants.DEFAULT_REACH;
		dim = Simulation.getInstance().getSizeVector().getDimension();
		buildReachVectors();
		Simulation.getInstance().addObserver(this);
	}
	
	/**
	 * This strategy has no parameters, this is a null object implementation.
	 * {@inheritDoc}
	 */
	@Override
	public void setParameter(String key, int value) {
		if (key.equals("Reach")) {
			reach = value;
		}
		buildReachVectors();
	}

	/**
	 * This strategy has no parameters, this is a null object implementation.
	 * {@inheritDoc}
	 */
	@Override
	public Map<String, Integer> getParameters() {
		HashMap<String, Integer> params = new HashMap<String, Integer>();
		params.put("Reach", reach);
		return params;
	}

	/* (non-Javadoc)
	 * @see wator.model.MovementStrategy#getName()
	 */
	@Override
	public String getName() {
		return "Random";
	}

	/**
	 * Returns random direction vectors. The neighborhood is not
	 * used here.
	 * {@inheritDoc}
	 */
	@Override
	public IntegerVector apply(Agent agent, Neighborhood neighborhood) {
		boolean valid;
		for (int i = 0; i < 100; i++) {
			IntegerVector v = getRandomVector();
			valid = true;
			for (Neighbor n : neighborhood) {
				if (n.getDistanceVector().equals(v)) {
					valid = false;
					break;
				}
			}
			if (valid) {
				return v;
			}
		}
		return nullVector;
	}
	
	/**
	 * Get a random vector in the configured range.
	 * 
	 * @return a random integer vector according to the range parameter 
	 */
	public IntegerVector getRandomVector() {
		return reachVector.generateRandomVectorInRange().sub(halfReachVector);
	}

	/**
	 * Reacts when the dimension of the size vector changes.
	 * {@inheritDoc}
	 */
	@Override
	public void update(Observable o, Object arg) {
		if (arg instanceof IntegerVector) {
			dim = ((IntegerVector) arg).getDimension();
			buildReachVectors();
		}
	}
	
	/* (non-javadoc)
	 * Builds the internal vectors reachVector and halfReachVector depending
	 * on the current reach value.
	 */
	private void buildReachVectors() {
		reachVector = IntegerVector.getCubeVector(dim, reach);
		halfReachVector = reachVector.div(2);
		reachVector = reachVector.add(IntegerVector.getCubeVector(dim, 1));
		nullVector = reachVector.sub(reachVector);
	}
}
